Kris Beevers adds Lowrance USR v4 support.
authorrobertlipe@gmail.com <robertlipe@gmail.com@f51c46e8-681c-474f-0cfe-069cfd0219fb>
Thu, 29 Mar 2012 00:16:48 +0000 (00:16 +0000)
committerrobertlipe@gmail.com <robertlipe@gmail.com@f51c46e8-681c-474f-0cfe-069cfd0219fb>
Thu, 29 Mar 2012 00:16:48 +0000 (00:16 +0000)
git-svn-id: http://gpsbabel.googlecode.com/svn/trunk@4164 f51c46e8-681c-474f-0cfe-069cfd0219fb

gpsbabel/lowranceusr4.c [new file with mode: 0644]

diff --git a/gpsbabel/lowranceusr4.c b/gpsbabel/lowranceusr4.c
new file mode 100644 (file)
index 0000000..f83164c
--- /dev/null
@@ -0,0 +1,1061 @@
+/*
+       Access to Lowrance USR version 4 files.
+       Contributed to gpsbabel by Kris Beevers (beevek at gmail.com)
+
+       Copyright (C) 2011 Robert Lipe, robertlipe@usa.net
+
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+
+       HISTORY:
+
+       01/06/2012 - Kris Beevers (beevek at gmail.com)
+       - First pass read-write support
+*/
+
+
+#include "defs.h"
+#include <string.h>
+#include <math.h> /* for lat/lon conversion */
+#include <time.h> /* for gmtime */
+
+/* from waypt.c, we need to iterate over waypoints when extracting
+   routes */
+extern queue waypt_head;
+
+static gbfile* file_in;
+static gbfile* file_out;
+static short_handle mkshort_handle;
+
+static route_head* trk_head;
+static route_head* rte_head;
+static int reading_version;
+
+static int waypt_uid;
+static int route_uid;
+static int track_uid;
+
+static waypoint **waypt_table;
+static int waypt_table_sz, waypt_table_ct;
+
+static char* opt_title;
+static char* opt_serialnum;
+static int opt_serialnum_i;
+static char* opt_content_descr;
+
+#define MYNAME "Lowrance USR4"
+
+#define MAXUSRSTRINGSIZE       256
+#define SEMIMINOR                 6356752.3142
+#define DEGREESTORADIANS       0.017453292
+
+
+typedef struct {
+  format_specific_data fs;
+  int uid_unit;
+  int uid_seq_low;
+  int uid_seq_high;
+} lowranceusr4_fsdata;
+
+
+static int
+lowranceusr4_readstr(char* buf, const int maxlen, gbfile* file, int bytes_per_char)
+{
+  int org, len;
+
+  org = len = gbfgetint32(file);
+  if (len < 0) {
+    buf[0] = '\0'; /* seems len=-1 means no string */
+    return 0;
+  } else if (len) {
+    if (len/bytes_per_char > maxlen) {
+      len = maxlen*bytes_per_char;
+    }
+    if (bytes_per_char == 1) {
+      (void) gbfread(buf, 1, len, file);
+    } else {
+      /* simple adjustment to read strings where characters are 16
+         bits (or more).  for now let's just project the characters
+         down onto utf-8 space by ignoring all but the most
+         significant byte. */
+      int i, j;
+      char discard;
+      for (i = 0; i < len/bytes_per_char; ++i) {
+        gbfread(&buf[i], 1, 1, file);
+        for (j = 1; j < bytes_per_char; ++j) {
+          gbfread(&discard, 1, 1, file);
+        }
+      }
+      buf[len/bytes_per_char] = '\0';
+    }
+    if (org > maxlen) {
+      (void) gbfseek(file, bytes_per_char * (org - maxlen), SEEK_CUR);
+    }
+  }
+
+  return len;
+}
+
+static void
+lowranceusr4_writestr(char *buf, gbfile *file, int bytes_per_char)
+{
+  int len = 0;
+
+  if (buf) {
+    len = strlen(buf);
+  }
+
+  if (0xffffffff / bytes_per_char < len) {
+    /* be pedantic and check for the unlikely event that we are asked
+       to write more than 2^32 bytes */
+    len = 0xffffffff / bytes_per_char;
+  }
+
+  gbfputint32(len*bytes_per_char, file_out);
+
+  if (bytes_per_char == 1) {
+    (void) gbfwrite(buf, 1, len, file);
+  } else {
+    int i, j;
+    for (i = 0; i < len; ++i) {
+      gbfputc(buf[i], file_out);
+      for (j = 1; j < bytes_per_char; ++j) {
+        gbfputc('\0', file_out);
+      }
+    }
+  }
+}
+
+
+static
+arglist_t lowranceusr4_args[] = {
+  {
+    "title", &opt_title, "(output) Output title string",
+    "", ARGTYPE_STRING, ARG_NOMINMAX
+  },
+  {
+    "serialnum", &opt_serialnum, "(output) Device serial number",
+    "0", ARGTYPE_INT, ARG_NOMINMAX
+  },
+  {
+    "description", &opt_content_descr, "(output) Content description",
+    "", ARGTYPE_STRING, ARG_NOMINMAX
+  },
+  ARG_TERMINATOR
+};
+
+static void
+rd_init(const char* fname)
+{
+  file_in = gbfopen_le(fname, "rb", MYNAME);
+}
+
+static void
+rd_deinit(void)
+{
+  gbfclose(file_in);
+}
+
+static void
+wr_init(const char* fname)
+{
+  file_out = gbfopen_le(fname, "wb", MYNAME);
+  mkshort_handle = mkshort_new_handle();
+}
+
+static void
+wr_deinit(void)
+{
+  gbfclose(file_out);
+  mkshort_del_handle(&mkshort_handle);
+}
+
+
+/**
+ * Latitude and longitude for USR coords are in the lowrance mercator meter
+ * format in WGS84.  The below code converts them to degrees.
+ */
+static double
+lon_mm_to_deg(double x)
+{
+  return x / (DEGREESTORADIANS * SEMIMINOR);
+}
+
+static double
+lat_mm_to_deg(double x)
+{
+  return (2.0 * atan(exp(x / SEMIMINOR)) - M_PI / 2.0) / DEGREESTORADIANS;
+}
+
+/* will be useful for write support */
+static long
+lon_deg_to_mm(double x)
+{
+  return (long)(x * SEMIMINOR * DEGREESTORADIANS);
+}
+
+static long
+lat_deg_to_mm(double x)
+{
+  return (long)(SEMIMINOR * log(tan((x * DEGREESTORADIANS + M_PI / 2.0) / 2.0)));
+}
+
+static time_t
+lowranceusr4_get_timestamp(int jd_number, time_t t)
+{
+  int a, b, c, d, e, m;
+  struct tm *ptm, ntm;
+  time_t out;
+
+  /* get UTC time from time_t */
+  ptm = gmtime(&t);
+  memset(&ntm, 0, sizeof(ntm));
+  ntm.tm_hour = ptm->tm_hour;
+  ntm.tm_min = ptm->tm_min;
+  ntm.tm_sec = ptm->tm_sec;
+
+  /* convert the JD number to get day/month/year */
+  a = jd_number + 32044;
+  b = (4*a + 3) / 146097;
+  c = a - (146097*b) / 4;
+  d = (4*c + 3) / 1461;
+  e = c - (1461*d) / 4;
+  m = (5*e + 2) / 153;
+  ntm.tm_mday = e + 1 - (153*m + 2) / 5;
+  ntm.tm_mon = m + 3 - 12 * (m / 10) - 1;
+  ntm.tm_year = 100 * b + d - 4800 + m / 10 - 1900;
+
+  /* put it all back together into a unix timestamp in UTC */
+  out = mkgmtime(&ntm);
+
+  return out;
+}
+
+static int
+lowranceusr4_jd_from_timestamp(time_t t)
+{
+  return (int)round((float)t / 86400.0 + 2440587.0);
+}
+
+
+static void
+lowranceusr4_copy_fsdata(lowranceusr4_fsdata **dest, lowranceusr4_fsdata *src)
+{
+  *dest = (lowranceusr4_fsdata *)xmalloc(sizeof(*src));
+  **dest = *src;
+  (*dest)->fs.next = NULL;
+}
+
+static void
+lowranceusr4_free_fsdata(void *fsdata)
+{
+  xfree(fsdata);
+}
+
+static
+lowranceusr4_fsdata *
+lowranceusr4_alloc_fsdata(void)
+{
+  lowranceusr4_fsdata *fsdata = (lowranceusr4_fsdata*) xcalloc(sizeof(*fsdata), 1);
+  fsdata->fs.type = FS_LOWRANCEUSR4;
+  fsdata->fs.copy = (fs_copy) lowranceusr4_copy_fsdata;
+  fsdata->fs.destroy = lowranceusr4_free_fsdata;
+  fsdata->fs.convert = NULL;
+
+  fsdata->uid_unit = 0;
+  fsdata->uid_seq_low = 0;
+  fsdata->uid_seq_high = 0;
+
+  return fsdata;
+}
+
+
+/* below couple of functions mostly borrowed from raymarine.c */
+
+/* make waypoint shortnames unique */
+static char
+same_points(const waypoint *A, const waypoint *B)
+{
+  return ( /* !!! We are case-sensitive !!! */
+           (strcmp(A->shortname, B->shortname) == 0) &&
+           (A->latitude == B->latitude) &&
+           (A->longitude == B->longitude));
+}
+
+static void
+register_waypt(const waypoint *ref)
+{
+  int i;
+  waypoint *wpt = (waypoint *) ref;
+
+  for (i = 0; i < waypt_table_ct; i++) {
+    waypoint *cmp = waypt_table[i];
+
+    if (same_points(wpt, cmp)) {
+      return;
+    }
+  }
+
+  if (waypt_table_ct >= waypt_table_sz) {
+    waypt_table_sz += 32;
+    if (waypt_table) {
+      waypt_table = (waypoint**) xrealloc(waypt_table, waypt_table_sz * sizeof(wpt));
+    } else {
+      waypt_table = (waypoint**) xmalloc(waypt_table_sz * sizeof(wpt));
+    }
+  }
+
+  if (global_opts.debug_level >= 2) {
+    printf(MYNAME " adding waypt %s (%s) to table at index %d\n",
+           wpt->shortname, wpt->description, waypt_table_ct);
+  }
+
+  waypt_table[waypt_table_ct] = (waypoint *)wpt;
+  waypt_table_ct++;
+}
+
+/* end borrowed from raymarine.c */
+
+static int
+lowranceusr4_find_waypt_index(const waypoint *wpt)
+{
+  int i;
+  for (i = 0; i < waypt_table_ct; ++i) {
+    if (same_points(wpt, (const waypoint *)waypt_table[i])) {
+      return i;
+    }
+  }
+  return waypt_table_ct+1; /* should never happen */
+}
+
+
+
+static void
+lowranceusr4_parse_waypoints(void)
+{
+  short int icon_num;
+  unsigned int i, num_waypts, create_date, create_time;
+  int text_len;
+  char buff[MAXUSRSTRINGSIZE + 1];
+
+  num_waypts = gbfgetint32(file_in);
+
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " parse_waypoints: Num waypoints %d\n", num_waypts);
+  }
+
+  for (i = 0; i < num_waypts; ++i) {
+    waypoint* wpt_tmp;
+
+    wpt_tmp = waypt_new();
+    lowranceusr4_fsdata *fsdata = lowranceusr4_alloc_fsdata();
+    fs_chain_add(&(wpt_tmp->fs), (format_specific_data *) fsdata);
+
+    /* read/parse waypoint, with fields as follows (taken mostly
+       from http://lowranceusrv4togpxconverter.blogspot.com/):
+
+         UID unit number             - uint32
+         UID sequence number         - int64
+         Waypt stream version number - uint16
+         Waypt name length (bytes)   - uint32
+         Waypoint name               - utf-16 string w/above length (w->shortname)
+         Longitude (mercator meters) - int32 (w->longitude)
+         Latitude (mercator meters)  - int32 (w->latitude)
+         Flags                       - uint32
+         Icon ID                     - uint16 (to w->icon_descr via conversion)
+         Color ID                    - uint16
+         Description length (bytes)  - uint32
+         Description                 - utf-16 string w/above length (w->description)
+         Alarm radius                - float (w->proximity)
+         Creation date               - uint32 (w->creation_time)
+         Creation time               - uint32 (w->creation_time)
+         Unused                      - uint8
+         Depth (feet)                - float (w->depth)
+         Loran GRI                   - int32
+         Loran TdA                   - int32
+         Loran TdB                   - int32
+     */
+
+    /* UID unit number */
+    fsdata->uid_unit = gbfgetint32(file_in);
+
+    /* 64-bit UID sequence number */
+    fsdata->uid_seq_low = gbfgetint32(file_in);
+    fsdata->uid_seq_high = gbfgetint32(file_in);
+
+    /* Waypt stream version number, discard for now */
+    gbfgetint16(file_in);
+
+    /* Waypoint name; input is 2 bytes per char, we convert to 1 */
+    text_len = lowranceusr4_readstr(&buff[0], MAXUSRSTRINGSIZE, file_in, 2);
+    if (text_len) {
+      buff[text_len] = '\0';
+      wpt_tmp->shortname = xstrdup(buff);
+    }
+
+    /* Long/Lat */
+    wpt_tmp->longitude = lon_mm_to_deg(gbfgetint32(file_in));
+    wpt_tmp->latitude = lat_mm_to_deg(gbfgetint32(file_in));
+
+    /* Flags, discard for now */
+    gbfgetint32(file_in);
+
+    /* Icon ID; TODO: need to run this through something like
+       lowranceusr_find_desc_from_icon_number to convert to a gpsbabel
+       icon description; however it doesn't seem that the icon ids
+       used in usr4 match those from usr{2,3} so we need a new
+       mapping. */
+    icon_num = gbfgetint16(file_in);
+    /* wpt_tmp->icon_descr = lowranceusr_find_desc_from_icon_number(icon_num); */
+
+    /* Color ID, discard for now */
+    gbfgetint16(file_in);
+
+    /* Waypoint descr; input is 2 bytes per char, we convert to 1 */
+    text_len = lowranceusr4_readstr(&buff[0], MAXUSRSTRINGSIZE, file_in, 2);
+    if (text_len) {
+      buff[text_len] = '\0';
+      wpt_tmp->description = xstrdup(buff);
+    }
+
+    /* Alarm radius; XXX: I'm not sure what the units are here,
+       assuming meters but may be feet? */
+    WAYPT_SET(wpt_tmp, proximity, gbfgetflt(file_in));
+
+    /* Creation date/time; the date is a Julian day number, and the
+       time is a unix timestamp. */
+    create_date = gbfgetint32(file_in);
+    create_time = gbfgetint32(file_in);
+    wpt_tmp->creation_time = lowranceusr4_get_timestamp(create_date, create_time);
+
+    /* Unused byte */
+    gbfgetc(file_in);
+
+    /* Depth in feet */
+    WAYPT_SET(wpt_tmp, depth, FEET_TO_METERS(gbfgetflt(file_in)));
+
+    /* Loran data, discard for now */
+    gbfgetint32(file_in);
+    gbfgetint32(file_in);
+    gbfgetint32(file_in);
+
+    if (global_opts.debug_level >= 1) {
+      printf(MYNAME " parse_waypoints: name = %s, uid_unit = %u, "
+             "uid_seq_low = %d, uid_seq_high = %d, lat = %f, lon = %f, depth = %f\n",
+             wpt_tmp->shortname, fsdata->uid_unit,
+             fsdata->uid_seq_low, fsdata->uid_seq_high,
+             wpt_tmp->latitude, wpt_tmp->longitude, wpt_tmp->depth);
+    }
+
+    waypt_add(wpt_tmp);
+  }
+}
+
+static waypoint*
+lowranceusr4_find_waypt(int uid_unit, int uid_seq_low, int uid_seq_high)
+{
+  queue *elem, *tmp;
+  waypoint *waypointp;
+  lowranceusr4_fsdata *fs = NULL;
+
+  QUEUE_FOR_EACH(&waypt_head, elem, tmp) {
+    waypointp = (waypoint *) elem;
+    fs = (lowranceusr4_fsdata *) fs_chain_find(waypointp->fs, FS_LOWRANCEUSR4);
+
+    if (fs && fs->uid_unit == uid_unit &&
+        fs->uid_seq_low == uid_seq_low &&
+        fs->uid_seq_high == uid_seq_high)
+    {
+      return waypointp;
+    }
+  }
+
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " lowranceusr4_find_waypt: warning, failed finding waypoint with ids %d %d %d\n",
+           uid_unit, uid_seq_low, uid_seq_high);
+  }
+  return NULL;
+}
+
+static void
+lowranceusr4_parse_routes(void)
+{
+  int num_routes, i, j, text_len;
+  unsigned int num_legs;
+  char buff[MAXUSRSTRINGSIZE + 1];
+  waypoint* wpt_tmp;
+  int uid_unit, uid_seq_low, uid_seq_high;
+
+  num_routes = gbfgetint32(file_in);
+
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " parse_routes: Num routes = %d\n", num_routes);
+  }
+
+  for (i = 0; i < num_routes; ++i) {
+    rte_head = route_head_alloc();
+    route_add_head(rte_head);
+    rte_head->rte_num = i+1;
+
+    lowranceusr4_fsdata *fsdata = lowranceusr4_alloc_fsdata();
+    fs_chain_add(&(rte_head->fs), (format_specific_data *) fsdata);
+
+    /* read/parse route, with fields as follows (taken mostly
+       from http://lowranceusrv4togpxconverter.blogspot.com/):
+
+         UID unit number             - uint32
+         UID sequence number         - int64
+         Route stream version number - uint16
+         Route name length (bytes)   - uint32
+         Route name                  - utf-16 string w/above length (r->rte_name)
+         Number of waypoints         - uint32 (N)
+         Waypoint list               - sequence of N (uint32, uint64) waypoint UIDs
+     */
+
+    /* UID unit number */
+    fsdata->uid_unit = gbfgetint32(file_in);
+
+    /* 64-bit UID sequence number */
+    fsdata->uid_seq_low = gbfgetint32(file_in);
+    fsdata->uid_seq_high = gbfgetint32(file_in);
+
+    /* Route stream version number, discard for now */
+    gbfgetint16(file_in);
+
+    /* Route name; input is 2 bytes per char, we convert to 1 */
+    text_len = lowranceusr4_readstr(&buff[0], MAXUSRSTRINGSIZE, file_in, 2);
+    if (text_len) {
+      buff[text_len] = '\0';
+      rte_head->rte_name = xstrdup(buff);
+    }
+
+    num_legs = gbfgetint32(file_in);
+
+    if (global_opts.debug_level >= 1) {
+      printf(MYNAME " parse_routes: route name=%s has %d waypoints\n",
+             rte_head->rte_name, num_legs);
+    }
+
+    for (j = 0; j < num_legs; ++j) {
+      uid_unit = gbfgetint32(file_in);
+      uid_seq_low = gbfgetint32(file_in);
+      uid_seq_high = gbfgetint32(file_in);
+      wpt_tmp = lowranceusr4_find_waypt(uid_unit, uid_seq_low, uid_seq_high);
+      if (wpt_tmp) {
+        if (global_opts.debug_level >= 2) {
+          printf(MYNAME " parse_routes: added wpt %s to route %s\n",
+                 wpt_tmp->shortname, rte_head->rte_name);
+        }
+        route_add_wpt(rte_head, waypt_dupe(wpt_tmp));
+      }
+    }
+
+    /* Mystery byte, discard */
+    gbfgetc(file_in);
+  }
+}
+
+static void
+lowranceusr4_parse_trails(void)
+{
+  int num_trails, num_trail_pts, M, i, j, k, trk_num, text_len;
+  char buff[MAXUSRSTRINGSIZE + 1];
+  waypoint* wpt_tmp;
+
+  /* num trails */
+  num_trails = gbfgetint32(file_in);
+
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " parse_trails: num trails = %d\n", num_trails);
+  }
+
+  for (i = trk_num = 0; i < num_trails; ++i) {
+    trk_head = route_head_alloc();
+    trk_head->rte_num = ++trk_num;
+    track_add_head(trk_head);
+
+    lowranceusr4_fsdata *fsdata = lowranceusr4_alloc_fsdata();
+    fs_chain_add(&(trk_head->fs), (format_specific_data *) fsdata);
+
+    /* read/parse trail, with fields as follows (taken mostly from
+       http://lowranceusrv4togpxconverter.blogspot.com/):
+
+         UID unit number             - uint32
+         UID sequence number         - int64
+         Trail stream version number - uint16
+         Trail name length (bytes)   - uint32
+         Trail name                  - utf-16 string w/above length (t->rte_name)
+         Flags                       - uint32
+         Color ID                    - uint32
+         Comment length (bytes)      - uint32
+         Comment                     - utf-16 string w/above length (t->rte_desc)
+         Creation date               - uint32
+         Creation time               - uint32
+         Unused                      - uint8
+         Active flag                 - uint8
+         Visible flag                - uint8
+         Data count (?)              - uint32
+         Data type depth (?)         - uint8
+         Data type water temp (?)    - uint8
+         Data type SOG (?)           - uint8
+         Trackpoint count            - int32 (N)
+         Trackpoint list             - sequence of N objects as follows:
+           Unknown (?)               - uint16
+           Unknown (?)               - uint8
+           POSIX timestamp (?)       - uint32 (w->creation_time)
+           Longitude (radians)       - double (w->longitude)
+           Latitude (radians)        - double (w->latitude)
+           Data item count           - uint32 (M)
+           Data items                - sequence of M objects as follows:
+             Unknown (?)             - uint8
+             Unknown (?)             - float
+     */
+
+    /* UID unit number */
+    fsdata->uid_unit = gbfgetint32(file_in);
+
+    /* 64-bit UID sequence number */
+    fsdata->uid_seq_low = gbfgetint32(file_in);
+    fsdata->uid_seq_high = gbfgetint32(file_in);
+
+    /* Trail stream version number, discard for now */
+    gbfgetint16(file_in);
+
+    /* Trail name; input is 2 bytes per char, we convert to 1 */
+    text_len = lowranceusr4_readstr(&buff[0], MAXUSRSTRINGSIZE, file_in, 2);
+    if (text_len) {
+      buff[text_len] = '\0';
+      trk_head->rte_name = xstrdup(buff);
+    }
+
+    /* Flags, discard for now */
+    gbfgetint32(file_in);
+
+    /* Color ID, discard for now */
+    gbfgetint32(file_in);
+
+    /* Comment/description */
+    text_len = lowranceusr4_readstr(&buff[0], MAXUSRSTRINGSIZE, file_in, 2);
+    if (text_len) {
+      buff[text_len] = '\0';
+      trk_head->rte_desc = buff;
+    }
+
+    /* Creation date/time, discard for now */
+    gbfgetint32(file_in);
+    gbfgetint32(file_in);
+
+    /* Some flag bytes, discard for now */
+    gbfgetc(file_in);
+    gbfgetc(file_in);
+    gbfgetc(file_in);
+
+    /* Some mysterious "data count" and "data type" stuff, not sure
+       what it's for, need dox */
+    gbfgetint32(file_in);
+    gbfgetc(file_in);
+    gbfgetc(file_in);
+    gbfgetc(file_in);
+
+    num_trail_pts = gbfgetint32(file_in);
+
+    if (global_opts.debug_level >= 1) {
+      printf(MYNAME " parse_trails: trail %d name=%s has %d trackpoints\n",
+             trk_num, trk_head->rte_name, num_trail_pts);
+    }
+
+    for (j = 0; j < num_trail_pts; ++j) {
+      wpt_tmp = waypt_new();
+
+      /* Some unknown bytes */
+      gbfgetint16(file_in);
+      gbfgetc(file_in);
+
+      /* POSIX timestamp */
+      wpt_tmp->creation_time = gbfgetint32(file_in);
+
+      /* Long/Lat */
+      wpt_tmp->longitude = gbfgetdbl(file_in) / DEGREESTORADIANS; /* rad to deg */
+      wpt_tmp->latitude = gbfgetdbl(file_in) / DEGREESTORADIANS;
+
+      /* Mysterious per-trackpoint data, toss it for now */
+      M = gbfgetint32(file_in);
+      for (k = 0; k < M; ++k) {
+        gbfgetc(file_in);
+        gbfgetflt(file_in);
+      }
+
+      track_add_wpt(trk_head, wpt_tmp);
+
+      if (global_opts.debug_level >= 2) {
+        printf(MYNAME " parse_routes: added trackpoint %f,%f to route %s\n",
+               wpt_tmp->latitude, wpt_tmp->longitude, trk_head->rte_name);
+      }
+    }
+  }
+}
+
+
+static void
+data_read(void)
+{
+  short int MajorVersion, MinorVersion;
+  int text_len, DataStreamVersion;
+  unsigned int create_date, create_time, serial_num;
+  unsigned char byte;
+  char buff[MAXUSRSTRINGSIZE + 1];
+
+
+  MajorVersion = gbfgetint16(file_in);
+  reading_version = MajorVersion;
+  MinorVersion = gbfgetint16(file_in);
+  DataStreamVersion = gbfgetint32(file_in);
+
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " data_read: Major Version %d Minor Version %d Data Stream Version %d\n",
+           MajorVersion, MinorVersion, DataStreamVersion);
+  }
+
+  if (MajorVersion != 4) {
+    fatal(MYNAME ": input file is from an unsupported version of the USR format (must be version 4)\n");
+  }
+
+  text_len = lowranceusr4_readstr(&buff[0], MAXUSRSTRINGSIZE, file_in, 1);
+  if (text_len > 0 && global_opts.debug_level >= 1) {
+    buff[text_len] = '\0';
+    printf(MYNAME " file title: %s\n", buff);
+  }
+
+  text_len = lowranceusr4_readstr(&buff[0], MAXUSRSTRINGSIZE, file_in, 1);
+  if (text_len > 0 && global_opts.debug_level >= 1) {
+    buff[text_len] = '\0';
+    printf(MYNAME " date string: %s\n", buff);
+  }
+
+  /* for now we won't use these for anything */
+  create_date = gbfgetint32(file_in);
+  create_time = gbfgetint32(file_in);
+  byte = gbfgetc(file_in); /* unused, apparently */
+
+  serial_num = gbfgetint32(file_in);
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " device serial number %u\n", (unsigned int)serial_num);
+  }
+
+  text_len = lowranceusr4_readstr(&buff[0], MAXUSRSTRINGSIZE, file_in, 1);
+  if (text_len > 0 && global_opts.debug_level >= 1) {
+    buff[text_len] = '\0';
+    printf(MYNAME " content description: %s\n", buff);
+  }
+
+  lowranceusr4_parse_waypoints();
+  lowranceusr4_parse_routes();
+  lowranceusr4_parse_trails();
+}
+
+
+static void
+lowranceusr4_waypt_disp(const waypoint* wpt)
+{
+  /* UID unit number */
+  gbfputint32(opt_serialnum_i, file_out);
+
+  /* 64-bit UID sequence number */
+  gbfputint32(waypt_uid++, file_out);
+  gbfputint32(0, file_out);
+
+  /* Waypt stream version number: this always seems to be 2 in my data
+     so that's what I'll use */
+  gbfputint16(2, file_out);
+
+  /* Waypt name */
+  lowranceusr4_writestr(wpt->shortname, file_out, 2);
+
+  /* Long/Lat */
+  gbfputint32(lon_deg_to_mm(wpt->longitude), file_out);
+  gbfputint32(lat_deg_to_mm(wpt->latitude), file_out);
+
+  /* Flags: this always seems to be 2 or 4 in my data, not sure what
+     it means */
+  gbfputint32(2, file_out);
+
+  /* Icon ID; TODO: need to invert icon description to an icon number,
+     see parse_waypoints above */
+  gbfputint16(0, file_out);
+
+  /* Color ID */
+  gbfputint16(0, file_out);
+
+  /* Waypt description */
+  lowranceusr4_writestr(wpt->description, file_out, 2);
+
+  /* Alarm radius */
+  gbfputflt(WAYPT_GET(wpt, proximity, 0.0), file_out);
+
+  /* Creation date/time */
+  gbfputint32(lowranceusr4_jd_from_timestamp(wpt->creation_time), file_out);
+  gbfputint32(wpt->creation_time, file_out);
+
+  /* Unused byte */
+  gbfputc(0, file_out);
+
+  /* Depth in feet */
+  gbfputflt(METERS_TO_FEET(WAYPT_GET(wpt, depth, 0.0)), file_out);
+
+  /* Loran data */
+  gbfputint32(0, file_out);
+  gbfputint32(0, file_out);
+  gbfputint32(0, file_out);
+}
+
+static void
+lowranceusr4_write_waypoints(void)
+{
+  int i;
+
+  /* enumerate all waypoints from both the plain old waypoint list and
+     also all routes */
+  waypt_table_sz = 0;
+  waypt_table_ct = 0;
+  waypt_table = NULL;
+  waypt_disp_all(register_waypt);
+  route_disp_all(NULL, NULL, register_waypt);
+
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " writing %d waypoints\n", waypt_table_ct);
+  }
+
+  gbfputint32(waypt_table_ct, file_out);
+  waypt_uid = 0;
+  for (i = 0; i < waypt_table_ct; ++i) {
+    if (global_opts.debug_level >= 2) {
+      printf(MYNAME " writing out waypt %d (%s - %s)\n",
+             i, waypt_table[i]->shortname, waypt_table[i]->description);
+    }
+    lowranceusr4_waypt_disp((const waypoint *)waypt_table[i]);
+  }
+}
+
+static void
+lowranceusr4_write_route_hdr(const route_head* rte)
+{
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " writing route #%d (%s) with %d waypts\n",
+           route_uid, rte->rte_name, rte->rte_waypt_ct);
+  }
+
+  /* UID unit number */
+  gbfputint32(opt_serialnum_i, file_out);
+
+  /* 64-bit UID sequence number */
+  gbfputint32(route_uid++, file_out);
+  gbfputint32(0, file_out);
+
+  /* Route stream version number: seems to be 1 in my data */
+  gbfputint16(1, file_out);
+
+  /* Waypt name */
+  lowranceusr4_writestr(rte->rte_name, file_out, 2);
+
+  /* Num waypoints */
+  gbfputint32(rte->rte_waypt_ct, file_out);
+}
+
+static void
+lowranceusr4_write_wpt_uids(const waypoint* wpt)
+{
+  int waypt_idx;
+
+  /* find the index of wpt in our table */
+  waypt_idx = lowranceusr4_find_waypt_index(wpt);
+  if (global_opts.debug_level >= 2) {
+    if (waypt_idx > waypt_table_ct) {
+      printf(MYNAME " WARNING: failed finding waypoint %s in waypoint table\n",
+             wpt->shortname);
+    } else {
+      printf(MYNAME " adding waypt %d (%s) to route\n",
+             waypt_idx, waypt_table[waypt_idx]->shortname);
+    }
+  }
+
+  gbfputint32(opt_serialnum_i, file_out);
+  gbfputint32(waypt_idx, file_out);
+  gbfputint32(0, file_out);
+}
+
+static void
+lowranceusr4_write_route_trl(const route_head* rte)
+{
+  /* Mystery byte */
+  gbfputc(0, file_out);
+}
+
+static void
+lowranceusr4_write_routes(void)
+{
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " writing %d routes\n", route_count());
+  }
+  gbfputint32(route_count(), file_out);
+  route_uid = 0;
+  route_disp_all(lowranceusr4_write_route_hdr,
+                 lowranceusr4_write_route_trl,
+                 lowranceusr4_write_wpt_uids);
+}
+
+static void
+lowranceusr4_write_track_hdr(const route_head* trk)
+{
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " writing track %d (%s) with %d trackpoints\n",
+           track_uid, trk->rte_name, trk->rte_waypt_ct);
+  }
+
+  /* UID unit number */
+  gbfputint32(opt_serialnum_i, file_out);
+
+  /* 64-bit UID sequence number */
+  gbfputint32(track_uid++, file_out);
+  gbfputint32(0, file_out);
+
+  /* Route stream version number: always seems to be 3 in my data */
+  gbfputint16(3, file_out);
+
+  /* Track name */
+  lowranceusr4_writestr(trk->rte_name, file_out, 2);
+
+  /* Flags: always seems to be 2 in my data */
+  gbfputint32(2, file_out);
+
+  /* Color ID */
+  gbfputint32(0, file_out);
+
+  /* Comment */
+  lowranceusr4_writestr(trk->rte_desc, file_out, 2);
+
+  /* Creation date/time */
+  gbfputint32(0, file_out);
+  gbfputint32(0, file_out);
+
+  /* Unused byte */
+  gbfputc(0, file_out);
+
+  /* Active flag */
+  gbfputc(0, file_out);
+
+  /* Visible flag; I'll just assume all tracks should be visible for
+     now */
+  gbfputc(1, file_out);
+
+  /* Mysterious "data count" and "data type" stuff */
+  gbfputint32(0, file_out);
+  gbfputc(0, file_out);
+  gbfputc(0, file_out);
+  gbfputc(0, file_out);
+
+  /* Trackpoint count */
+  gbfputint32(trk->rte_waypt_ct, file_out);
+}
+
+static void
+lowranceusr4_write_track_waypt(const waypoint* wpt)
+{
+  /* Some unknown bytes */
+  gbfputint16(0, file_out);
+  gbfputc(0, file_out);
+
+  /* Timestamp */
+  gbfputint32(wpt->creation_time, file_out);
+
+  /* Long/Lat */
+  gbfputdbl(wpt->longitude * DEGREESTORADIANS, file_out);
+  gbfputdbl(wpt->latitude * DEGREESTORADIANS, file_out);
+
+  /* Mysterious per-trackpoint data; we'll just say there are "0"
+     mystery entries */
+  gbfputint32(0, file_out);
+}
+
+static void
+lowranceusr4_write_trails(void)
+{
+  if (global_opts.debug_level >= 1) {
+    printf(MYNAME " writing %d tracks\n", track_count());
+  }
+  gbfputint32(track_count(), file_out);
+  track_uid = 0;
+  track_disp_all(lowranceusr4_write_track_hdr, NULL, lowranceusr4_write_track_waypt);
+}
+
+static void
+data_write(void)
+{
+  short int MajorVersion, MinorVersion;
+  int DataStreamVersion;
+  time_t now;
+  struct tm *now_tm;
+  char buf[256];
+
+  setshort_length(mkshort_handle, 15);
+
+  MajorVersion = 4;
+  MinorVersion = 0;
+  DataStreamVersion = 10;
+
+  gbfputint16(MajorVersion, file_out);
+  gbfputint16(MinorVersion, file_out);
+  gbfputint32(DataStreamVersion, file_out);
+
+  /* file title */
+  lowranceusr4_writestr(opt_title, file_out, 1);
+
+  /* date string */
+  now = time(NULL);
+  now_tm = gmtime(&now);
+  sprintf(buf, "%d/%d/%d", now_tm->tm_mon+1, now_tm->tm_mday, now_tm->tm_year+1900);
+  lowranceusr4_writestr(buf, file_out, 1);
+
+  /* creation date/time */
+  gbfputint32(lowranceusr4_jd_from_timestamp(now), file_out);
+  gbfputint32(now, file_out);
+
+  /* unused byte */
+  gbfputc(0, file_out);
+
+  /* device serial number */
+  opt_serialnum_i = atoi(opt_serialnum);
+  gbfputint32(opt_serialnum_i, file_out);
+
+  /* content description */
+  lowranceusr4_writestr(opt_content_descr, file_out, 1);
+
+  lowranceusr4_write_waypoints();
+  lowranceusr4_write_routes();
+  lowranceusr4_write_trails();
+}
+
+
+ff_vecs_t lowranceusr4_vecs = {
+  ff_type_file,
+  FF_CAP_RW_ALL,
+  rd_init,
+  wr_init,
+  rd_deinit,
+  wr_deinit,
+  data_read,
+  data_write,
+  NULL,
+  lowranceusr4_args,
+  CET_CHARSET_ASCII, 0 /* CET-REVIEW */
+};